《Android 基础(四十五)》 RecyclerView.ItemDecoration源码浅析

前言

RecyclerView目前来说,是日常开发中使用最多的控件,功能强大而且复杂。而Item Decoration作为RecyclerView开发过程中不可或缺的部分,需要深入的了解一下。

源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/**
* An ItemDecoration allows the application to add a special drawing and layout offset
* to specific item views from the adapter's data set. This can be useful for drawing dividers
* between items, highlights, visual grouping boundaries and more.
*
* <p>All ItemDecorations are drawn in the order they were added, before the item
* views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
* and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
* RecyclerView.State)}.</p>
*/
public abstract static class ItemDecoration {
/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn before the item views are drawn,
* and will thus appear underneath the views.
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}

/**
* @deprecated
* Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
*/
@Deprecated
public void onDraw(Canvas c, RecyclerView parent) {
}

/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn after the item views are drawn
* and will thus appear over the views.
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView.
*/
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
}

/**
* @deprecated
* Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
*/
@Deprecated
public void onDrawOver(Canvas c, RecyclerView parent) {
}


/**
* @deprecated
* Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
*/
@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}

/**
* Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
* the number of pixels that the item view should be inset by, similar to padding or margin.
* The default implementation sets the bounds of outRect to 0 and returns.
*
* <p>
* If this ItemDecoration does not affect the positioning of item views, it should set
* all four fields of <code>outRect</code> (left, top, right, bottom) to zero
* before returning.
*
* <p>
* If you need to access Adapter for additional data, you can call
* {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
* View.
*
* @param outRect Rect to receive the output.
* @param view The child view to decorate
* @param parent RecyclerView this ItemDecoration is decorating
* @param state The current state of RecyclerView.
*/
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}

注意看英文注释,ItemDecoration是用于装饰RecyclerView的Item的,主要关注如下三个方法

getItemOffsets

参数 意义
outRect ItemView的外围矩形
view ItemView
parent RecyclerView,ItemView的Parent
state RecyclerView的状态

直接这样子说看不出效果,上几张图区分一下

1
2
3
4
5
outRect.top =20;
outRect.bottom =20;

outRect.left=60;
outRect.right =60;

这里写图片描述

1
2
3
4
5
outRect.top =20;
outRect.bottom =20;

outRect.left=60;
outRect.right =0;

这里写图片描述

1
2
3
4
5
outRect.top =60;
outRect.bottom =60;

outRect.left=20;
outRect.right =20;

这里写图片描述
看上去设置outRect设置top,left,right,bottom有一种padding的效果,但是实际上只是它是ItemView外围的空间,你可以将其理解成RecyclerView针对每个ItemView设置的Padding,而不是ItemView自身的padding。

onDraw

参数 意义
c RecyclerView的Canvas
parent RecyclerView,ItemView的Parent
state RecyclerView的状态

onDraw操作是在RecyclerView所在的画布上绘制内容,也就是在ItemView的背后绘制内容,重叠部分会被ItemView遮挡。
比如我们在Item View的背后画一个圆圈圈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public YdDividerItemDecoration(){
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
mPaint.setAntiAlias(true);
}


@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
Logger.d("yidong -- onDraw");

int width = parent.getWidth();
int height = parent.getHeight();

int radius = Math.min(width, height)/2;

c.drawCircle(width/2, height/2, radius, mPaint);
}

这里写图片描述
这个是为了说明,我们操作的画布是RecyclerView的画布,而不是针对ItemView,所以一般情况下,我们需要针对每个ItemView做操作,比如画分割线,官方给出的DividerItemDecoration的onDraw方法

1
2
3
4
5
6
7
8
9
10
11
12

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getLayoutManager() == null || mDivider == null) {
return;
}
if (mOrientation == VERTICAL) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void drawVertical(Canvas canvas, RecyclerView parent) {
canvas.save();
final int left;
final int right;
//noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
if (parent.getClipToPadding()) {
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
canvas.clipRect(left, parent.getPaddingTop(), right,
parent.getHeight() - parent.getPaddingBottom());
} else {
left = 0;
right = parent.getWidth();
}

final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(child, mBounds);
final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
final int top = bottom - mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
canvas.restore();
}

可以看出都是便利RecyclerView的Item来做操作的。

onDrawOver

参数 意义
c RecyclerView的Canvas
parent RecyclerView,ItemView的Parent
state RecyclerView的状态

参数类型和意义和onDraw一模一样,但是效果确实在RecyclerView的ItemView之上。
还是换个圆圈圈,代码和上面一样,只是移动到onDrawOver方法下执行。
这里写图片描述
可以很明显的看到,我们的圆圈圈覆盖了ItemView。至于谁覆盖谁,显然是绘制顺序的问题。
这里写图片描述
可以看到执行顺序,onDrawOver是在onDraw之后的,但是ItemView的绘制是否在这个两个操作之前,从最后的效果来看,是的,但是需要代码证实。直接看RecyclerView绘制相关的方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void draw(Canvas c) {
//这里是RecyclerView的绘制过程,和View的绘制过程类似,先是背景,然后是调用
//recyclerview的onDraw方法绘制自身,然后通过dispatchDraw来绘制子View
// 而onDraw(下面)执行的就是ItemDecoration的onDraw方法,所以顺序很明显了。
//ItemDecoration-> ItemView -> ItemDecoration.onDrawOver
super.draw(c);
// 执行ItemDecoration onDrawOver方法,最顶层
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
……
1
2
3
4
5
6
7
8
9
10
@Override
public void onDraw(Canvas c) {
super.onDraw(c);

// 执行ItemDecoration的onDraw方法,最底层
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}

总结

看清楚了ItemDecoration的实现原理,具体通过什么方式绘制出什么样的图形,该只是设计和时间的问题,Github上有不错的一些实现,大家有空可以去学习借鉴下。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×